package backup

import (
	"bufio"
	"bytes"
	"compress/gzip"
	"dump/config"
	"dump/utils"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

const (
	backupTimeout = 23 * time.Hour
)

// 执行备份
func PerformBackup(cfg *config.MySQLConfig) error {
	// 创建备份目录
	if err := os.MkdirAll(cfg.BackupDir, os.ModePerm); err != nil {
		return fmt.Errorf("创建备份目录失败: %v", err)
	}

	timestamp := time.Now().Format("20060102150405")
	backupFile, cmdArgs, err := buildBackupCommand(cfg, timestamp)
	if err != nil {
		return err
	}
	// fmt.Printf("cmdArgs: %v\n", cmdArgs)

	// 构建完整命令，使用 > 重定向输出到文件
	cmd := exec.Command(cfg.DumpPath, cmdArgs...)
	fmt.Printf("cmd: %v\n", cmd)
	// 捕获标准错误
	var stderr bytes.Buffer
	cmd.Stderr = &stderr

	output, err := os.Create(backupFile)
	if err != nil {
		return fmt.Errorf("创建备份文件失败: %v", err)
	}
	defer output.Close()

	cmd.Stdout = output // 标准输出写入文件
	// fmt.Printf("cmd: %v\n", cmd)

	// 启动命令
	if err := cmd.Start(); err != nil {
		return fmt.Errorf("启动mysqldump失败: %v\n%s", err, stderr.String())
	}

	// 设置超时监控
	done := make(chan error, 1)
	go func() {
		done <- cmd.Wait()
	}()

	select {
	case err := <-done:
		if err != nil {
			// 在错误信息中包含 stderr
			return fmt.Errorf("mysqldump执行失败: %v\n%s", err, stderr.String())
		}
	case <-time.After(backupTimeout):
		cmd.Process.Kill()
		return fmt.Errorf("备份操作超时\n最后错误输出: %s", stderr.String())
	}

	// 验证备份完整性
	if err := verifyBackup(backupFile); err != nil {
		return fmt.Errorf("备份验证失败: %v", err)
	}

	utils.Log(utils.INFO, "备份成功完成: %s", backupFile)
	// 压缩备份文件

	output.Close()

	// 压缩备份文件
	if cfg.CompressLevel > 0 {
		// 压缩文件
		compressedFile, err := compressBackupFile(backupFile, cfg.CompressLevel)
		if err != nil {
			return fmt.Errorf("备份压缩失败: %v", err)
		}

		// 安全删除原始文件
		if err := safeDeleteFile(backupFile, 8); err == nil {
			utils.Log(utils.INFO, "已删除原始备份文件: %s", backupFile)
		} else {
			utils.Log(utils.INFO, "保留原始备份文件: %s，原因: %v", backupFile, err)
		}

		utils.Log(utils.INFO, "压缩成功: %s (压缩级别 %d)", compressedFile, cfg.CompressLevel)
	}
	return nil
}

// 生成备份命令
func buildBackupCommand(cfg *config.MySQLConfig, timestamp string) (string, []string, error) {
	var backupFile string
	var cmdArgs []string

	// 解析mysqldump基础参数
	cmdArgs = append(cmdArgs,
		"-h", cfg.Host,
		"-P", cfg.Port,
		"-u", cfg.User,
		"--password="+cfg.Password)

	// 添加额外选项
	// if cfg.Options != "" {

	// 	options := strings.Split(cfg.Options, " ")
	// 	cmdArgs = append(cmdArgs, options...)
	// }

	if cfg.Options != "" {
		parts := strings.Split(cfg.Options, " ")
		for _, part := range parts {
			if part != "" { // 跳过空字符串
				cmdArgs = append(cmdArgs, part)
				// fmt.Printf("part: %v\n", part)
			}
		}
	}

	// 根据备份类型构建参数
	switch cfg.BackupType {
	case "full":
		backupFile = filepath.Join(cfg.BackupDir, fmt.Sprintf("full_%s.sql", timestamp))
		cmdArgs = append(cmdArgs, "-A")
	case "database":
		if len(cfg.Databases) == 0 {
			return "", nil, fmt.Errorf("未指定需要备份的数据库")
		}
		backupFile = filepath.Join(cfg.BackupDir, fmt.Sprintf("db_%s.sql", timestamp))
		cmdArgs = append(cmdArgs, "-B")
		cmdArgs = append(cmdArgs, cfg.Databases...)
	case "table":
		if len(cfg.Databases) != 1 || len(cfg.Tables) == 0 {
			return "", nil, fmt.Errorf("单表备份需要指定1个数据库和至少1张表")
		}
		backupFile = filepath.Join(cfg.BackupDir, fmt.Sprintf("tbl_%s.sql", timestamp))
		cmdArgs = append(cmdArgs, cfg.Databases[0])
		cmdArgs = append(cmdArgs, cfg.Tables...)
	default:
		return "", nil, fmt.Errorf("不支持的备份类型: %s", cfg.BackupType)
	}

	return backupFile, cmdArgs, nil
}
func compressBackupFile(filePath string, compressionLevel int) (string, error) {
	// 如果压缩级别为0，表示不压缩
	if compressionLevel == 0 {
		return filePath, nil
	}

	// 验证压缩级别范围 (1-9)
	if compressionLevel < 1 || compressionLevel > 9 {
		return "", fmt.Errorf("无效的压缩级别 %d，必须在1-9之间", compressionLevel)
	}

	// 创建压缩后的文件名
	compressedFile := filePath + ".gz"

	// 打开原始文件
	srcFile, err := os.Open(filePath)
	if err != nil {
		return "", fmt.Errorf("无法打开备份文件: %v", err)
	}
	defer srcFile.Close()
	// 创建压缩文件
	dstFile, err := os.Create(compressedFile)
	if err != nil {
		return "", fmt.Errorf("无法创建压缩文件: %v", err)
	}
	defer dstFile.Close()

	// 创建gzip写入器并设置压缩级别
	gzWriter, err := gzip.NewWriterLevel(dstFile, compressionLevel)
	if err != nil {
		return "", fmt.Errorf("无法创建压缩器: %v", err)
	}
	defer gzWriter.Close()
	// 分块复制数据，控制内存使用
	buf := make([]byte, 10*1024*1024) // 缓冲区
	for {
		n, err := srcFile.Read(buf)
		if err != nil && err != io.EOF {

			return "", fmt.Errorf("读取备份文件失败: %v", err)
		}
		if n == 0 {
			break
		}

		if _, err := gzWriter.Write(buf[:n]); err != nil {

			return "", fmt.Errorf("写入压缩文件失败: %v", err)
		}
	}

	// 关闭压缩器以确保所有数据刷入文件
	if err := gzWriter.Close(); err != nil {
		return "", fmt.Errorf("关闭压缩器失败: %v", err)
	}

	// 验证压缩文件
	if err := verifyCompressedFile(compressedFile); err != nil {
		os.Remove(compressedFile) // 删除无效压缩文件
		return "", err
	}
	srcFile.Close()
	return compressedFile, nil
}

// 验证压缩文件完整性
func verifyCompressedFile(filePath string) error {
	// 检查文件是否存在
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return fmt.Errorf("压缩文件未生成")
	}

	// 检查文件大小
	fileInfo, err := os.Stat(filePath)
	if err != nil {
		return fmt.Errorf("无法获取压缩文件信息: %v", err)
	}

	// 确保压缩文件不是空的
	if fileInfo.Size() == 0 {
		return fmt.Errorf("压缩文件大小为0")
	}

	// 尝试打开压缩文件进行解压测试
	file, err := os.Open(filePath)
	if err != nil {
		return fmt.Errorf("无法打开压缩文件: %v", err)
	}
	defer file.Close()

	// 创建gzip读取器
	gzReader, err := gzip.NewReader(file)
	if err != nil {
		return fmt.Errorf("压缩文件损坏: %v", err)
	}
	defer gzReader.Close()

	// 读取部分数据以验证
	buf := make([]byte, 1024)
	if _, err := gzReader.Read(buf); err != nil && err != io.EOF {
		return fmt.Errorf("压缩文件读取失败: %v", err)
	}

	return nil
}

func verifyBackup(filePath string) error {
	file, err := os.Open(filePath)
	if err != nil {
		return fmt.Errorf("无法打开备份文件: %v", err)
	}
	defer file.Close()

	const (
		lastLinesNeeded = 3    // 需要检查的最后行数
		bufferSize      = 4096 // 读取缓冲区大小
	)

	// 获取文件信息
	stat, err := file.Stat()
	if err != nil {
		return fmt.Errorf("获取文件信息失败: %v", err)
	}

	fileSize := stat.Size()
	if fileSize == 0 {
		return fmt.Errorf("备份文件为空")
	}

	// 从文件末尾开始读取
	pos := fileSize - 1
	lines := make([]string, 0, lastLinesNeeded)
	buf := make([]byte, 1)

	for pos >= 0 && len(lines) < lastLinesNeeded {
		// 定位到当前位置
		_, err := file.Seek(pos, io.SeekStart)
		if err != nil {
			return fmt.Errorf("文件定位失败: %v", err)
		}

		// 读取一个字节
		_, err = file.Read(buf)
		if err != nil {
			return fmt.Errorf("文件读取失败: %v", err)
		}

		// 遇到换行符时读取行内容
		if buf[0] == '\n' {
			line, err := readLineAtPos(file, pos+1)
			if err != nil {
				return fmt.Errorf("读取行内容失败: %v", err)
			}
			if len(line) > 0 { // 忽略空行
				lines = append(lines, line)
			}
		}
		pos--
	}

	// 处理文件开头的情况（行数不足时）
	if len(lines) < lastLinesNeeded {
		line, err := readLineAtPos(file, 0)
		if err != nil {
			return fmt.Errorf("读取首行失败: %v", err)
		}
		if len(line) > 0 {
			lines = append(lines, line)
		}
	}

	// 反转行顺序，因为我们是从后往前读取的
	for i, j := 0, len(lines)-1; i < j; i, j = i+1, j-1 {
		lines[i], lines[j] = lines[j], lines[i]
	}

	// 定义备份完成标记
	completionMarkers := []string{
		"Dump completed",
		"Dump completed on",
		"-- Dump completed",
		"-- MySQL dump completed",
	}

	// 检查最后几行中是否包含备份完成标记
	for _, line := range lines {
		for _, marker := range completionMarkers {
			if strings.Contains(line, marker) {
				return nil // 验证通过
			}
		}
	}

	return fmt.Errorf("备份文件不完整，未找到完成标记。最后%d行内容: %q",
		len(lines), strings.Join(lines, " | "))
}

// readLineAtPos 从指定位置读取一行
func readLineAtPos(file *os.File, pos int64) (string, error) {
	_, err := file.Seek(pos, io.SeekStart)
	if err != nil {
		return "", err
	}
	reader := bufio.NewReader(file) // 在这里创建reader
	line, err := reader.ReadString('\n')
	if err != nil && err != io.EOF {
		return "", err
	}
	return strings.TrimRight(line, "\n"), nil
}

// readLineAtPos 从指定位置读取一行
// func readLineAtPos(file *os.File, pos int64) (string, error) {
// 	_, err := file.Seek(pos, io.SeekStart)
// 	if err != nil {
// 		return "", err
// 	}
// 	reader := bufio.NewReader(file)
// 	line, err := reader.ReadString('\n')
// 	if err != nil && err != io.EOF {
// 		return "", err
// 	}
// 	return strings.TrimRight(line, "\n"), nil
// }

// 删除函数
func safeDeleteFile(filePath string, maxAttempts int) error {
	const baseDelay = 1 * time.Second
	var lastError error

	if maxAttempts < 1 {
		maxAttempts = 1
	}

	// 多次尝试删除文件
	for attempt := 1; attempt <= maxAttempts; attempt++ {
		if f, err := os.Open(filePath); err == nil {
			f.Close()
		}
		err := os.Remove(filePath)
		if err == nil {
			return nil // 成功删除
		}

		// 检查是否是文件不存在
		if os.IsNotExist(err) {
			return nil // 文件已不存在
		}

		// 记录错误
		lastError = err

		// 计算指数增长的延迟
		delay := baseDelay * time.Duration(attempt*attempt)

		// 日志记录
		utils.Log(utils.INFO, "删除文件失败 [%d/%d] - %v", attempt, maxAttempts, err)
		utils.Log(utils.INFO, "将在 %.1f 秒后重试...", delay.Seconds())

		// 等待文件锁定释放
		time.Sleep(delay)
	}

	// 使用正确的变量名 lastError
	return fmt.Errorf("无法删除文件: %v", lastError)
}

func CleanOldBackups(backupDir string, expireDays int) {
	utils.Log(utils.INFO, "开始清理超过%d天的备份文件", expireDays)

	files, err := os.ReadDir(backupDir)
	if err != nil {
		utils.Log(utils.ERROR, "读取备份目录失败: %v", err)
		return
	}

	cutOffTime := time.Now().AddDate(0, 0, -expireDays)
	deleted := 0

	for _, file := range files {
		if file.IsDir() || !strings.HasSuffix(file.Name(), ".sql") {
			continue
		}

		info, err := file.Info()
		if err != nil {
			utils.Log(utils.ERROR, "获取文件信息失败: %s, %v", file.Name(), err)
			continue
		}

		if info.ModTime().Before(cutOffTime) {
			fullPath := filepath.Join(backupDir, file.Name())
			if err := os.Remove(fullPath); err != nil {
				utils.Log(utils.ERROR, "删除旧备份失败: %s, %v", file.Name(), err)
			} else {
				utils.Log(utils.INFO, "已删除过期备份: %s", file.Name())
				deleted++
			}
		}
	}

	utils.Log(utils.INFO, "备份清理完成，共删除%d个文件", deleted)
}
